package dataStructures;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import umontreal.iro.lecuyer.probdistmulti.MultinomialDist;

import math.Fraction;

public class Triple  {
	
	private int max;
	private int mid;
	private int min;
	private List<Triple> levelList;
	private boolean levelSet;
	
	public Triple(){
		//Root Triple
		this.setMax(0);
		this.setMid(0);
		this.setMin(0);
	}
	
	public Triple(int max,int mid,int min){
		this.setMax(max);
		this.setMid(mid);
		this.setMin(min);
		sortTriple();
	}

	public Triple(int max,int mid,int min,boolean setLevel){
		this.setMax(max);
		this.setMid(mid);
		this.setMin(min);
		sortTriple();
		this.levelList= new ArrayList<Triple>();
		if(setLevel==true){
			this.setLevelList();
		}
	}
	public int sumTriple(){
		return (this.getMax()+this.getMid()+this.getMin());
	}
	
	private void sortTriple() {
		int tempMax= Math.max(this.getMax(), this.getMid());
		int max= Math.max(tempMax, this.getMin());
		int tempMin= Math.min(this.getMax(), this.getMid());
		int min= Math.min(tempMin, this.getMin());
		int mid=(this.sumTriple()-(max)-(min));
		this.setMax(max);
		this.setMid(mid);
		this.setMin(min);
	}

	public int getMax() {
		return max;
	}
	public void setMax(int max) {
		this.max = max;
	}
	public int getMid() {
		return mid;
	}
	public void setMid(int mid) {
		this.mid = mid;
	}
	public int getMin() {
		return min;
	}
	public void setMin(int min) {
		this.min = min;
	}

	public double getClonalityMeasure(){
		if(this.sumTriple()==0){
			return 0.0;
		}else{
		
		int midMin= this.getMid()+this.getMin();
		int sum = this.sumTriple();
	//	System.out.println(midMin);
	//	System.out.println(sum);
		Fraction f= new Fraction(midMin, sum);
	//	int res= (midMin/sum);
	//	System.out.println(f);
		return f.asDouble();
		}
		
	}
	
	private double gcmZero(){
		return 0.0;
	}
	
	public double getProb(){
		int[] triple = new int[3];
		triple[0]= this.getMax();
		triple[1]=this.getMid();
		triple[2]=this.getMin();
		double[] prob =  new double[3];
		prob[0]=new Fraction(1,3).asDouble();
		prob[1]=new Fraction(1,3).asDouble();
		prob[2]=new Fraction(1,3).asDouble();
		
		double result=MultinomialDist.prob(this.sumTriple(), prob, triple);
		return result*this.findMultiplyer();
	}
	
	/**
	 * A Method that returns the multiplier to sort the triples.
	 * @param triple is a triple to have its multiplier found
	 * @return the result of the multiplier
	 */
	protected int findMultiplyer(){
		int mult = 0;
		if (!compairThreeInt(this.getMax(),this.getMid(),this.getMin())){ // returns true if compairThreeInt returns false
			mult=6;}// All positions are different
		if (twoInEqual(this.getMax(),this.getMid(),this.getMin()))// returns true if only 2 are equal
			mult=3;	
		 if((this.getMax()==this.getMid())&&(this.getMid()==this.getMin()))// All are the same
			 mult=1;// don't need to multiple	
		return mult;
		
	}
	
	public double getNormedClonality(){
		if(this.getClonalityMeasure()==0.0&& this.getExcpetedClonality()==0.0)
			return 0.0;
			else
		return this.getClonalityMeasure()/this.getExcpetedClonality();
	}
		
	/**
	 * A method that compairs integers 
	 * @param x an integer
	 * @param y an integer
	 * @return is true is x=y
	 */
	protected boolean compairInt(int x, int y){
		return (x==y);//returns true if x is equal to y
	}
	
	/**
	 * A method that compairs three integers
	 * @param x an integer
	 * @param y an integer
	 * @param z an integer
	 * @return returns true is x=y=z
	 */
	protected boolean compairThreeInt(int x, int y, int z){
		return (compairInt(x,y)&&compairInt(y,z)&&compairInt(z,x));// returns true if x,y,z are equal
	}
	
	/**
	 * A method that to test if two int are equal in a set
	 * @param x an integer
	 * @param y an integer
	 * @param z an integer
	 * @return returns true is two in a set are equal
	 */
	protected boolean twoInEqual(int x, int y, int z){
		// returns true is two of x y z are the same but not all three
		return((compairInt(x,y)||compairInt(y,z)||compairInt(x,z))&&(!compairThreeInt(x,y,z))); 
	}
	
	
	private void setLevelList(){
		Level levelMachine = new Level();
		this.levelList.addAll(levelMachine.searchFor(this));
		this.levelSet= true;
	}
	
	public double getExcpetedClonality(){
		if(this.sumTriple()==0){
			return 0.0;
		}else{
		if(this.levelSet==true){
			Iterator<Triple> thisIter = this.levelList.iterator();	
		double result =0;
		while(thisIter.hasNext()){
			Triple currentTrip =thisIter.next();
			double CM=currentTrip.getClonalityMeasure();
			double pr = currentTrip.getProb();
			result=result+(CM*pr);
		}
//		System.out.println(this.displayTriple()+ " has an expected Clonality of "+ result);
		return result;
		}else return getEClonality();
		}
	}
		
	private double getEClonality(){
		List<Triple> currentLevel = new ArrayList<Triple>();
		Level levelMachine = new Level();
		currentLevel.addAll(levelMachine.searchFor(this));
		Iterator<Triple> iter = currentLevel.iterator();
		double result =0;
		while(iter.hasNext()){
			Triple currentTrip =iter.next();
			double CM=currentTrip.getClonalityMeasure();
			double pr = currentTrip.getProb();
			result=result+(CM*pr);
			}
		return result;
	}

	public double getMaxClonality(){
		if(this.levelSet==true){
			Iterator<Triple> iter = this.levelList.iterator();
			List<Double> clonalityList = new ArrayList<Double>();
			while(iter.hasNext()){
				clonalityList.add(iter.next().getClonalityMeasure());
			}
			Iterator<Double> cmIter = clonalityList.iterator();
			double max =0;
			while(cmIter.hasNext()){
				max = Math.max(max, cmIter.next().doubleValue());
			}
			return max;
		}else{
			return getMC();
		}
		
		
	}
		
	private double getMC(){
		List<Triple> currentLevel = new ArrayList<Triple>();
		List<Double> clonalityList = new ArrayList<Double>();
		Level levelMachine = new Level();
		currentLevel.addAll(levelMachine.searchFor(this));
		Iterator<Triple> iter = currentLevel.iterator();
		while(iter.hasNext()){
			clonalityList.add(iter.next().getClonalityMeasure());
		}
		Iterator<Double> cmIter = clonalityList.iterator();
		double max =0;
		while(cmIter.hasNext()){
			max = Math.max(max, cmIter.next().doubleValue());
		}
		return max;
	}
	
	public String displayTriple(){
		return "("+this.getMin()+","+this.getMid()+","+this.getMax()+")";
	}
	
	public boolean equals(Object triple){
		Triple trip = (Triple)triple;
		boolean equal= false;
		if((trip.getMax()==this.getMax())&&
		   (trip.getMid()==this.getMid())&&
		   (trip.getMin()==this.getMin())
		   ){
			equal= true;
	//		System.out.println(this.displayTriple()+" is equal to "+ trip.displayTriple());
		}
		
		return equal;
	}
	
	public int hashCode(){
		int maxhash = 10000;
		int midhash = 5000;
		int minhash = 10;
		
		maxhash=this.getMax()+maxhash;
		midhash= this.getMid()+midhash;
		minhash= this.getMin()+minhash;
		
		return maxhash+midhash+minhash;
	}
	
	

}
